﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using ExplorerService.Common.DataContracts.Operations;
using ExplorerService.Common.DataContracts.Operations.OperationProgress;
using ExplorerService.Common.ServiceContracts;

namespace ExplorerService.Managers
{
    /// <summary>
    /// Singleton used to dispatch and managed operations between clients and applications
    /// </summary>
    public sealed class OperationOrchestrator
    {
        /// <summary>
        /// Stores the current orchestrator instance
        /// </summary>
        static OperationOrchestrator _instance;

        /// <summary>
        /// Object used as a lock for simple thread safety
        /// </summary>
        static readonly object Padlock = new object();

        /// <summary>
        /// Default constructor
        /// </summary>
        OperationOrchestrator() { }

        /// <summary>
        /// Gets the current orchestrator instance
        /// </summary>
        public static OperationOrchestrator Instance
        {
            get
            {
                lock (Padlock)
                {
                    return _instance ?? (_instance = new OperationOrchestrator());
                }
            }
        }

        /// <summary>
        /// Keeps track of operations
        /// </summary>
        private readonly List<Operation> _operations = new List<Operation>();



        /// <summary>
        /// Adds the operation to the list of operations waiting to be picked up by applications
        /// </summary>
        public bool QueueOperationForApplication(Operation operation)
        {
            operation.OperationCallback = OperationContext.Current.GetCallbackChannel<IClientOperationServiceCallback>();
            _operations.Add(operation);
            BroadcastNewOperationAvailable(operation.Id);
            return true;
        }

        /// <summary>
        /// Triggers the operation available event
        /// </summary>
        /// <param name="operationId"></param>
        private void BroadcastNewOperationAvailable(Guid operationId)
        {
            if (OperationAvailable != null)
            {
                OperationAvailable(operationId);
            }
        }


        /// <summary>
        /// Waits for an available operation for an application and sends it to the application
        /// </summary>
        /// <param name="applicationSessionId"></param>
        /// <returns></returns>
        public Operation NextOperation(Guid applicationSessionId)
        {
            Operation operationRequest = null;
            var finishedWaitEvent = new AutoResetEvent(false);
            var thread = new Thread(
                () =>
                {
                    for (var attempts = 0; attempts < 20; attempts++)
                    {
                        operationRequest = GetNextOperationForApplicationSession(applicationSessionId);
                        if (operationRequest != null)
                        {
                            finishedWaitEvent.Set();
                            return;
                        }
                        Thread.Sleep(200);
                    }
                }) { IsBackground = true };
            thread.Start();
            finishedWaitEvent.WaitOne(5000);
            if(operationRequest!=null)
            {
                if(operationRequest.OperationsToExecute.Count>0)
                {
                    LockSession(applicationSessionId, operationRequest.ClientSessionId);
                }
            }
            return operationRequest;
        }

        /// <summary>
        /// Locks an application session to prevent operation collisions
        /// </summary>
        /// <param name="applicationSessionId"></param>
        /// <param name="clientSessionId"></param>
        private void LockSession(Guid applicationSessionId, Guid clientSessionId)
        {
            ApplicationSessionManager.Instance.LockApplicationSession(applicationSessionId, clientSessionId);
        }

        /// <summary>
        /// Unlocks an application session
        /// </summary>
        /// <param name="applicationSessionId"></param>
        private void UnlockSession(Guid applicationSessionId)
        {
            ApplicationSessionManager.Instance.UnlockApplicationSession(applicationSessionId);
        }

        /// <summary>
        /// Gets the next available request from the queue for a specific application
        /// </summary>
        private Operation GetNextOperationForApplicationSession(Guid applicationSessionId)
        {
            var pendingRequest = _operations.FirstOrDefault(
                    request => request.ApplicationSessionId == applicationSessionId && !request.Sent);
            return pendingRequest;
        }




        /// <summary>
        /// Posts the progress to the operation client
        /// </summary>
        public void HandleOperationProgress(OperationProgress operationProgress)
        {
            if (operationProgress.OperationId == Guid.Empty) return;
            var matchOperation =
                (from op in Instance._operations where op.Id == operationProgress.OperationId select op).FirstOrDefault();
            if (matchOperation == null) return;
            PostProgressToClient(operationProgress, matchOperation);

        }

        /// <summary>
        /// Posts the operation progress to the client and removes the operation if completed
        /// </summary>
        /// <param name="operationProgress"></param>
        /// <param name="operation"></param>
        private static void PostProgressToClient(OperationProgress operationProgress, Operation operation)
        {
            if (operationProgress is OperationCompleted || operationProgress is OperationFailedDueToException)
            {
                Instance._operations.Remove(operation);
                Instance.UnlockSession(operation.ApplicationSessionId);
            }
            operation.OperationCallback.OperationProgress(operation.ApplicationSessionId, operationProgress);
        }

        

        /// <summary>
        /// Sets the CancelPending flag for an operation to true
        /// </summary>
        /// <param name="operationId"></param>
        public void CancelOperation(Guid operationId)
        {
            var matchOperation =
                (from op in Instance._operations where op.Id == operationId select op).FirstOrDefault();
            if (matchOperation == null) return;
            matchOperation.CancelPending = true;
        }

        /// <summary>
        /// Gets the CancelPending flag for an operation
        /// </summary>
        public bool OperationCancelPending(Guid operationId)
        {
            var matchOperation =
               (from op in Instance._operations where op.Id == operationId select op).FirstOrDefault();
            return matchOperation != null && matchOperation.CancelPending;
        }

        /// <summary>
        /// Triggered when a new operation is available
        /// </summary>
        public event OperationAvailableHandler OperationAvailable;
        public delegate void OperationAvailableHandler(Guid operationId);
    }
}
